package org.codehaus.activemq.message;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageFormatException;
import javax.jms.MessageNotWriteableException;
import org.codehaus.activemq.service.MessageIdentity;
import org.codehaus.activemq.util.IdGenerator;

public class ActiveMQMessage extends AbstractPacket
  implements Message, Comparable
{
  static final int DEFAULT_DELIVERY_MODE = 2;
  static final int DEFAULT_PRIORITY = 4;
  static final long DEFAULT_TIME_TO_LIVE = 0L;
  static final byte EOF = 2;
  static final byte BYTES = 3;
  static final byte STRING = 4;
  static final byte BOOLEAN = 5;
  static final byte CHAR = 6;
  static final byte BYTE = 7;
  static final byte SHORT = 8;
  static final byte INT = 9;
  static final byte LONG = 10;
  static final byte FLOAT = 11;
  static final byte DOUBLE = 12;
  static final byte NULL = 13;
  static final int CORRELATION_INDEX = 0;
  static final int TYPE_INDEX = 1;
  static final int BROKER_NAME_INDEX = 2;
  static final int CLUSTER_NAME_INDEX = 3;
  static final int TRANSACTION_ID_INDEX = 4;
  static final int REPLY_TO_INDEX = 5;
  static final int TIMESTAMP_INDEX = 6;
  static final int EXPIRATION_INDEX = 7;
  static final int REDELIVERED_INDEX = 8;
  static final int XA_TRANS_INDEX = 9;
  static final int CID_INDEX = 10;
  static final int PROPERTIES_INDEX = 11;
  static final int PAYLOAD_INDEX = 12;
  protected boolean readOnlyMessage;
  private String jmsMessageID;
  private String jmsClientID;
  private String jmsCorrelationID;
  private ActiveMQDestination jmsDestination;
  private ActiveMQDestination jmsReplyTo;
  private int jmsDeliveryMode = 2;
  private boolean jmsRedelivered;
  private String jmsType;
  private long jmsExpiration;
  private int jmsPriority = 4;
  private long jmsTimestamp;
  private Hashtable properties;
  private boolean readOnlyProperties;
  private String entryBrokerName;
  private String entryClusterName;
  private int[] consumerNos;
  private String producerID;
  private String transactionId;
  private boolean xaTransacted;
  private String consumerId;
  private boolean messageConsumed;
  private MessageAcknowledge messageAcknowledge;
  private byte[] bodyAsBytes;
  private MessageIdentity messageIdentity;

  public boolean isJMSMessage()
  {
    return true;
  }

  public String toString()
  {
    return getPacketTypeAsString(getPacketType()) + ": dest = " + this.jmsDestination + ", id = " + this.jmsMessageID;
  }

  public MessageAcknowledge getMessageAcknowledge()
  {
    return this.messageAcknowledge;
  }

  public void setMessageAcknowledge(MessageAcknowledge messageAcknowledge)
  {
    this.messageAcknowledge = messageAcknowledge;
  }

  public int getPacketType()
  {
    return 6;
  }

  public String getId()
  {
    return this.jmsMessageID;
  }

  public void setId(String newId)
  {
    this.jmsMessageID = newId;
  }

  public void setReadOnly(boolean value)
  {
    this.readOnlyProperties = value;
    this.readOnlyMessage = value;
  }

  public boolean isConsumerTarget(int consumerNumber)
  {
    if (this.consumerNos != null) {
      for (int i = 0; i < this.consumerNos.length; i++) {
        if (this.consumerNos[i] == consumerNumber) {
          return true;
        }
      }
    }
    return false;
  }

  public int hashCode()
  {
    return this.jmsMessageID != null ? this.jmsMessageID.hashCode() : super.hashCode();
  }

  public boolean equals(Object obj)
  {
    boolean result = obj == this;
    if ((!result) && (obj != null) && ((obj instanceof ActiveMQMessage))) {
      ActiveMQMessage other = (ActiveMQMessage)obj;
      result = (this.jmsMessageID == other.jmsMessageID) || ((this.jmsMessageID != null) && (other.jmsMessageID != null) && (this.jmsMessageID.equals(other.jmsMessageID)));
    }

    return result;
  }

  public int compareTo(Object o)
  {
    if ((o instanceof ActiveMQMessage)) {
      return compareTo((ActiveMQMessage)o);
    }
    return -1;
  }

  public int compareTo(ActiveMQMessage that)
  {
    int answer = 1;

    if ((that != null) && (this.jmsDestination != null) && (that.jmsDestination != null)) {
      answer = this.jmsDestination.compareTo(that.jmsDestination);
      if (answer == 0) {
        if ((this.jmsMessageID != null) && (that.jmsMessageID != null)) {
          answer = IdGenerator.compare(this.jmsMessageID, that.jmsMessageID);
        }
        else {
          answer = 1;
        }
      }
    }
    return answer;
  }

  public ActiveMQMessage shallowCopy()
    throws JMSException
  {
    ActiveMQMessage other = new ActiveMQMessage();
    initializeOther(other);
    return other;
  }

  public ActiveMQMessage deepCopy()
    throws JMSException
  {
    return shallowCopy();
  }

  public boolean isExpired(long currentTime)
    throws JMSException
  {
    boolean result = false;
    long expiration = getJMSExpiration();
    if ((expiration > 0L) && (expiration < currentTime)) {
      result = true;
    }
    return result;
  }

  public boolean isExpired() throws JMSException {
    return isExpired(System.currentTimeMillis());
  }

  protected void initializeOther(ActiveMQMessage other)
  {
    other.jmsMessageID = this.jmsMessageID;
    other.jmsClientID = this.jmsClientID;
    other.jmsCorrelationID = this.jmsCorrelationID;
    other.jmsDestination = this.jmsDestination;
    other.jmsReplyTo = this.jmsReplyTo;
    other.jmsDeliveryMode = this.jmsDeliveryMode;
    other.jmsRedelivered = this.jmsRedelivered;
    other.jmsType = this.jmsType;
    other.jmsExpiration = this.jmsExpiration;
    other.jmsPriority = this.jmsPriority;
    other.jmsTimestamp = this.jmsTimestamp;
    other.properties = this.properties;
    other.readOnlyProperties = this.readOnlyProperties;
    other.readOnlyMessage = this.readOnlyMessage;
    other.entryBrokerName = this.entryBrokerName;
    other.entryClusterName = this.entryClusterName;
    other.consumerNos = this.consumerNos;
    other.producerID = this.producerID;
    other.transactionId = this.transactionId;
    other.xaTransacted = this.xaTransacted;
    other.bodyAsBytes = this.bodyAsBytes;
    other.messageAcknowledge = this.messageAcknowledge;
    other.messageIdentity = this.messageIdentity;
  }

  public String getJMSMessageID()
  {
    return this.jmsMessageID;
  }

  public void setJMSMessageID(String id)
  {
    this.jmsMessageID = id;
  }

  public long getJMSTimestamp()
  {
    return this.jmsTimestamp;
  }

  public void setJMSTimestamp(long timestamp)
  {
    this.jmsTimestamp = timestamp;
  }

  public byte[] getJMSCorrelationIDAsBytes()
  {
    return this.jmsCorrelationID != null ? this.jmsCorrelationID.getBytes() : null;
  }

  public void setJMSCorrelationIDAsBytes(byte[] correlationID)
  {
    if (correlationID == null) {
      this.jmsCorrelationID = null;
    }
    else
      this.jmsCorrelationID = new String(correlationID);
  }

  public void setJMSCorrelationID(String correlationID)
  {
    this.jmsCorrelationID = correlationID;
  }

  public String getJMSCorrelationID()
  {
    return this.jmsCorrelationID;
  }

  public Destination getJMSReplyTo()
  {
    return this.jmsReplyTo;
  }

  public void setJMSReplyTo(Destination replyTo)
  {
    this.jmsReplyTo = ((ActiveMQDestination)replyTo);
  }

  public Destination getJMSDestination()
  {
    return this.jmsDestination;
  }

  public void setJMSDestination(Destination destination)
  {
    this.jmsDestination = ((ActiveMQDestination)destination);
  }

  public int getJMSDeliveryMode()
  {
    return this.jmsDeliveryMode;
  }

  public void setJMSDeliveryMode(int deliveryMode)
  {
    this.jmsDeliveryMode = deliveryMode;
  }

  public boolean getJMSRedelivered()
  {
    return this.jmsRedelivered;
  }

  public void setJMSRedelivered(boolean redelivered)
  {
    this.jmsRedelivered = redelivered;
  }

  public String getJMSType()
  {
    return this.jmsType;
  }

  public void setJMSType(String type)
  {
    this.jmsType = type;
  }

  public long getJMSExpiration()
  {
    return this.jmsExpiration;
  }

  public void setJMSExpiration(long expiration)
  {
    this.jmsExpiration = expiration;
  }

  public int getJMSPriority()
  {
    return this.jmsPriority;
  }

  public void setJMSPriority(int priority)
  {
    this.jmsPriority = priority;
  }

  public synchronized void clearProperties()
  {
    if (this.properties != null) {
      this.properties.clear();
    }
    this.readOnlyProperties = false;
  }

  public boolean propertyExists(String name)
  {
    return this.properties != null ? this.properties.containsKey(name) : false;
  }

  public boolean getBooleanProperty(String name)
    throws JMSException
  {
    return vanillaToBoolean(this.properties, name);
  }

  public byte getByteProperty(String name)
    throws JMSException
  {
    return vanillaToByte(this.properties, name);
  }

  public short getShortProperty(String name)
    throws JMSException
  {
    return vanillaToShort(this.properties, name);
  }

  public int getIntProperty(String name)
    throws JMSException
  {
    return vanillaToInt(this.properties, name);
  }

  public long getLongProperty(String name)
    throws JMSException
  {
    return vanillaToLong(this.properties, name);
  }

  public float getFloatProperty(String name)
    throws JMSException
  {
    return vanillaToFloat(this.properties, name);
  }

  public double getDoubleProperty(String name)
    throws JMSException
  {
    return vanillaToDouble(this.properties, name);
  }

  public String getStringProperty(String name)
    throws JMSException
  {
    return vanillaToString(this.properties, name);
  }

  public Object getObjectProperty(String name)
  {
    return this.properties != null ? this.properties.get(name) : null;
  }

  public Enumeration getPropertyNames()
  {
    if (this.properties == null) {
      this.properties = new Hashtable();
    }
    return this.properties.keys();
  }

  public Hashtable getProperties()
  {
    return this.properties;
  }

  void setProperties(Hashtable newProperties)
  {
    this.properties = newProperties;
  }

  public void setBooleanProperty(String name, boolean value)
    throws JMSException
  {
    prepareProperty(name);
    this.properties.put(name, value ? Boolean.TRUE : Boolean.FALSE);
  }

  public void setByteProperty(String name, byte value)
    throws JMSException
  {
    prepareProperty(name);
    this.properties.put(name, new Byte(value));
  }

  public void setShortProperty(String name, short value)
    throws JMSException
  {
    prepareProperty(name);
    this.properties.put(name, new Short(value));
  }

  public void setIntProperty(String name, int value)
    throws JMSException
  {
    prepareProperty(name);
    this.properties.put(name, new Integer(value));
  }

  public void setLongProperty(String name, long value)
    throws JMSException
  {
    prepareProperty(name);
    this.properties.put(name, new Long(value));
  }

  public void setFloatProperty(String name, float value)
    throws JMSException
  {
    prepareProperty(name);
    this.properties.put(name, new Float(value));
  }

  public void setDoubleProperty(String name, double value)
    throws JMSException
  {
    prepareProperty(name);
    this.properties.put(name, new Double(value));
  }

  public void setStringProperty(String name, String value)
    throws JMSException
  {
    prepareProperty(name);
    this.properties.put(name, value);
  }

  public void setObjectProperty(String name, Object value)
    throws JMSException
  {
    prepareProperty(name);
    if (((value instanceof Number)) || ((value instanceof Character)) || ((value instanceof Boolean)) || ((value instanceof String)))
    {
      this.properties.put(name, value);
    }
    else
      throw new MessageFormatException("Cannot set property to type: " + value.getClass().getName());
  }

  public void acknowledge()
    throws JMSException
  {
    if (this.messageAcknowledge != null)
      this.messageAcknowledge.acknowledge();
  }

  public void clearBody()
    throws JMSException
  {
    this.readOnlyMessage = false;
    this.bodyAsBytes = null;
  }

  boolean vanillaToBoolean(Hashtable table, String name) throws JMSException {
    boolean result = false;
    if (table != null) {
      Object value = table.get(name);
      if (value != null) {
        if ((value instanceof Boolean)) {
          result = ((Boolean)value).booleanValue();
        }
        else if ((value instanceof String))
        {
          result = Boolean.valueOf((String)value).booleanValue();
        }
        else {
          throw new MessageFormatException(name + " not a Boolean type");
        }
      }
    }
    return result;
  }

  byte vanillaToByte(Hashtable table, String name) throws JMSException {
    byte result = 0;
    if (table != null) {
      Object value = table.get(name);
      if (value != null) {
        if ((value instanceof Byte)) {
          result = ((Byte)value).byteValue();
        }
        else if ((value instanceof String)) {
          result = Byte.valueOf((String)value).byteValue();
        }
        else {
          throw new MessageFormatException(name + " not a Byte type");
        }
      }
      else
      {
        throw new NumberFormatException("Cannot interpret null as a Byte");
      }
    }
    return result;
  }

  short vanillaToShort(Hashtable table, String name) throws JMSException {
    short result = 0;
    if (table != null) {
      Object value = table.get(name);
      if (value != null) {
        if ((value instanceof Short)) {
          result = ((Short)value).shortValue();
        } else {
          if ((value instanceof String)) {
            return Short.valueOf((String)value).shortValue();
          }
          if ((value instanceof Byte)) {
            result = (short)((Byte)value).byteValue();
          }
          else
            throw new MessageFormatException(name + " not a Short type");
        }
      }
      else {
        throw new NumberFormatException(name + " is null");
      }
    }
    return result;
  }

  int vanillaToInt(Hashtable table, String name) throws JMSException {
    int result = 0;
    if (table != null) {
      Object value = table.get(name);
      if (value != null) {
        if ((value instanceof Integer)) {
          result = ((Integer)value).intValue();
        }
        else if ((value instanceof String)) {
          result = Integer.valueOf((String)value).intValue();
        }
        else if ((value instanceof Byte)) {
          result = ((Byte)value).intValue();
        }
        else if ((value instanceof Short)) {
          result = ((Short)value).intValue();
        }
        else {
          throw new MessageFormatException(name + " not an Integer type");
        }
      }
      else {
        throw new NumberFormatException(name + " is null");
      }
    }
    return result;
  }

  long vanillaToLong(Hashtable table, String name) throws JMSException {
    long result = 0L;
    if (table != null) {
      Object value = table.get(name);
      if (value != null) {
        if ((value instanceof Long)) {
          result = ((Long)value).longValue();
        }
        else if ((value instanceof String))
        {
          result = Long.valueOf((String)value).longValue();
        }
        else if ((value instanceof Byte)) {
          result = ((Byte)value).byteValue();
        }
        else if ((value instanceof Short)) {
          result = ((Short)value).shortValue();
        }
        else if ((value instanceof Integer)) {
          result = ((Integer)value).intValue();
        }
        else {
          throw new MessageFormatException(name + " not a Long type");
        }
      }
      else {
        throw new NumberFormatException(name + " is null");
      }
    }
    return result;
  }

  float vanillaToFloat(Hashtable table, String name) throws JMSException {
    float result = 0.0F;
    if (table != null) {
      Object value = table.get(name);
      if (value != null) {
        if ((value instanceof Float)) {
          result = ((Float)value).floatValue();
        }
        else if ((value instanceof String)) {
          result = Float.valueOf((String)value).floatValue();
        }
        else {
          throw new MessageFormatException(name + " not a Float type: " + value.getClass());
        }
      }
      else {
        throw new NullPointerException(name + " is null");
      }
    }
    return result;
  }

  double vanillaToDouble(Hashtable table, String name) throws JMSException {
    double result = 0.0D;
    if (table != null) {
      Object value = table.get(name);
      if (value != null) {
        if ((value instanceof Double)) {
          result = ((Double)value).doubleValue();
        }
        else if ((value instanceof String)) {
          result = Double.valueOf((String)value).doubleValue();
        }
        else if ((value instanceof Float)) {
          result = ((Float)value).floatValue();
        }
        else {
          throw new MessageFormatException(name + " not a Double type");
        }
      }
      else {
        throw new NullPointerException(name + " is null");
      }
    }
    return result;
  }

  String vanillaToString(Hashtable table, String name) throws JMSException
  {
    String result = null;
    if (table != null) {
      Object value = table.get(name);
      if (value != null) {
        if (((value instanceof String)) || ((value instanceof Number)) || ((value instanceof Boolean))) {
          result = value.toString();
        }
        else {
          throw new MessageFormatException(name + " not a String type");
        }
      }
    }
    return result;
  }

  private void prepareProperty(String name) throws JMSException {
    if ((name == null) || (name.length() == 0)) {
      throw new IllegalArgumentException("Invalid name: " + name);
    }
    if (this.readOnlyProperties) {
      throw new MessageNotWriteableException("Properties are read-only");
    }
    if (this.properties == null)
      this.properties = new Hashtable();
  }

  public String getEntryBrokerName()
  {
    return this.entryBrokerName;
  }

  public void setEntryBrokerName(String newEntryBrokerName)
  {
    this.entryBrokerName = newEntryBrokerName;
  }

  public String getEntryClusterName()
  {
    return this.entryClusterName;
  }

  public void setEntryClusterName(String newEntryClusterName)
  {
    this.entryClusterName = newEntryClusterName;
  }

  public int[] getConsumerNos()
  {
    return this.consumerNos;
  }

  public void setConsumerNos(int[] newConsumerNos)
  {
    this.consumerNos = newConsumerNos;
  }

  public String getJMSClientID()
  {
    return this.jmsClientID;
  }

  public void setJMSClientID(String newJmsClientID)
  {
    this.jmsClientID = newJmsClientID;
  }

  public String getProducerID()
  {
    return this.producerID;
  }

  public void setProducerID(String newProducerID)
  {
    this.producerID = newProducerID;
  }

  public boolean isPartOfTransaction()
  {
    return (this.transactionId != null) && (this.transactionId.length() > 0);
  }

  public String getTransactionId()
  {
    return this.transactionId;
  }

  public void setTransactionId(String newTransactionId)
  {
    this.transactionId = newTransactionId;
  }

  public String getConsumerId()
  {
    return this.consumerId;
  }

  public void setConsumerId(String consId)
  {
    this.consumerId = consId;
  }

  public boolean isMessageConsumed()
  {
    return this.messageConsumed;
  }

  public void setMessageConsumed(boolean messageConsumed)
  {
    this.messageConsumed = messageConsumed;
  }

  public final void convertBodyToBytes()
    throws IOException
  {
    if (this.bodyAsBytes == null) {
      ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
      DataOutputStream dataOut = new DataOutputStream(bytesOut);
      writeBody(dataOut);
      dataOut.flush();
      this.bodyAsBytes = bytesOut.toByteArray();
      dataOut.close();
    }
  }

  public final void buildBodyFromBytes()
    throws IOException
  {
    if (this.bodyAsBytes != null) {
      ByteArrayInputStream bytesIn = new ByteArrayInputStream(this.bodyAsBytes);
      DataInputStream dataIn = new DataInputStream(bytesIn);
      readBody(dataIn);
      dataIn.close();
    }
  }

  protected void writeBody(DataOutput dataOut)
    throws IOException
  {
  }

  protected void readBody(DataInput dataIn)
    throws IOException
  {
  }

  public byte[] getBodyAsBytes()
    throws IOException
  {
    if (this.bodyAsBytes == null) {
      convertBodyToBytes();
    }
    return this.bodyAsBytes;
  }

  public void setBodyAsBytes(byte[] bodyAsBytes)
  {
    this.bodyAsBytes = bodyAsBytes;
  }

  protected void writeMapProperties(Hashtable table, DataOutput dataOut)
    throws IOException
  {
    Enumeration iter;
    if (table != null) {
      dataOut.writeShort(table.size());
      for (iter = table.keys(); iter.hasMoreElements(); ) {
        String key = iter.nextElement().toString();
        dataOut.writeUTF(key);
        Object value = table.get(key);

        if ((value instanceof byte[])) {
          byte[] data = (byte[])value;
          dataOut.write(3);
          if (data != null) {
            dataOut.writeInt(data.length);
            dataOut.write(data);
          }
          else {
            dataOut.writeInt(-1);
          }
        }
        else if ((value instanceof Byte)) {
          dataOut.write(7);
          Byte v = (Byte)value;
          dataOut.writeByte(v.byteValue());
        }
        else if ((value instanceof Boolean)) {
          dataOut.write(5);
          Boolean v = (Boolean)value;
          dataOut.writeBoolean(v.booleanValue());
        }
        else if ((value instanceof String)) {
          dataOut.write(4);
          dataOut.writeUTF(value.toString());
        }
        else if ((value instanceof Character)) {
          dataOut.write(6);
          Character v = (Character)value;
          dataOut.writeChar(v.charValue());
        }
        else if ((value instanceof Number)) {
          Number v = (Number)value;

          if ((value instanceof Long)) {
            dataOut.write(10);
            dataOut.writeLong(v.longValue());
          }
          else if ((value instanceof Integer)) {
            dataOut.write(9);
            dataOut.writeInt(v.intValue());
          }
          else if ((value instanceof Short)) {
            dataOut.write(8);
            dataOut.writeShort(v.shortValue());
          }
          else if ((value instanceof Float)) {
            dataOut.write(11);
            dataOut.writeFloat(v.floatValue());
          }
          else if ((value instanceof Double)) {
            dataOut.write(12);
            dataOut.writeDouble(v.doubleValue());
          }
        }
        else {
          throw new RuntimeException("Do not know how to parse value of type: " + value.getClass());
        }
      }
    }
    else
    {
      dataOut.writeShort(-1);
    }
  }

  protected Hashtable readMapProperties(DataInput dataIn)
    throws IOException
  {
    Hashtable result = null;
    int size = dataIn.readShort();
    if (size > -1) {
      result = new Hashtable();
      for (int i = 0; i < size; i++) {
        String key = dataIn.readUTF();
        Object value = null;
        int type = dataIn.readByte();
        if (type == 3) {
          byte[] data = null;
          int dataSize = dataIn.readInt();
          if (dataSize > -1) {
            data = new byte[dataSize];
            dataIn.readFully(data);
          }
          value = data;
        }
        else if (type == 7) {
          value = new Byte(dataIn.readByte());
        }
        else if (type == 5) {
          value = dataIn.readBoolean() ? Boolean.TRUE : Boolean.FALSE;
        }
        else if (type == 4) {
          value = dataIn.readUTF();
        }
        else if (type == 6) {
          value = new Character(dataIn.readChar());
        }
        else if (type == 10) {
          value = new Long(dataIn.readLong());
        }
        else if (type == 9) {
          value = new Integer(dataIn.readInt());
        }
        else if (type == 8) {
          value = new Short(dataIn.readShort());
        }
        else if (type == 11) {
          value = new Float(dataIn.readFloat());
        }
        else if (type == 12) {
          value = new Double(dataIn.readDouble());
        }
        else {
          throw new RuntimeException("Do not know how to parse type: " + type);
        }
        result.put(key, value);
      }
    }
    return result;
  }

  public boolean isXaTransacted()
  {
    return this.xaTransacted;
  }

  public void setXaTransacted(boolean xaTransacted)
  {
    this.xaTransacted = xaTransacted;
  }

  public ActiveMQDestination getJMSActiveMQDestination() {
    return this.jmsDestination;
  }

  public MessageIdentity getJMSMessageIdentity()
  {
    if (this.messageIdentity == null) {
      this.messageIdentity = new MessageIdentity(this.jmsMessageID);
    }
    return this.messageIdentity;
  }

@Override
public <T> T getBody(Class<T> arg0) throws JMSException {
	// TODO Auto-generated method stub
	return null;
}

@Override
public long getJMSDeliveryTime() throws JMSException {
	// TODO Auto-generated method stub
	return 0;
}

@Override
public boolean isBodyAssignableTo(Class arg0) throws JMSException {
	// TODO Auto-generated method stub
	return false;
}

@Override
public void setJMSDeliveryTime(long arg0) throws JMSException {
	// TODO Auto-generated method stub
	
}
}